﻿using Grow.Domain.EventBus;
using Grow.Domain.Object;
using Grow.Module.Service;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Diagnostics;

namespace Grow.Domain.Repository.EntityFrameworkCore
{
    public class DbContextBase : DbContext, IDbContext
    {
        private readonly IServiceContext _serviceContext;
        private readonly IDomainEventPublisher _publisherHandler;
        public DbContextBase(DbContextOptions options, IServiceContext serviceContext) : base(options)
        {
            _serviceContext = serviceContext;
            _publisherHandler = _serviceContext.GetRequiredService<IDomainEventPublisher>();
        }

        #region OnConfiguring(配置)
        /// <summary>
        /// 配置
        /// </summary>
        /// <param name="builder">配置生成器</param>
        protected override void OnConfiguring(DbContextOptionsBuilder builder)
        {
            _ = builder.ConfigureWarnings(x => x.Ignore(
                    RelationalEventId.ConnectionOpening,
                    RelationalEventId.ConnectionOpened,
                    RelationalEventId.DataReaderDisposing,
                    RelationalEventId.ConnectionClosing,
                    RelationalEventId.ConnectionClosed,
                    CoreEventId.ServiceProviderCreated,
                    CoreEventId.ServiceProviderDebugInfo,
                    CoreEventId.SensitiveDataLoggingEnabledWarning,
                    CoreEventId.ContextInitialized,
                    CoreEventId.ContextDisposed,
                    CoreEventId.QueryCompilationStarting,
                    CoreEventId.QueryCompilationStarting,
                    CoreEventId.QueryExecutionPlanned,
                    CoreEventId.StartedTracking,
                    CoreEventId.DetectChangesStarting,
                    CoreEventId.DetectChangesCompleted,
                    CoreEventId.SaveChangesStarting,
                    CoreEventId.PropertyChangeDetected,
                    RelationalEventId.TransactionStarted,
                    RelationalEventId.TransactionDisposed
                ));

            _ = builder.EnableSensitiveDataLogging();
            _ = builder.EnableDetailedErrors();
        }
        #endregion


        #region SaveChangesAsync 保存
        public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
        {
            await SaveDomainEventAsync(ChangeTracker, cancellationToken);

            SaveChangesBefore();

            int rows = await base.SaveChangesAsync(cancellationToken);

            await PublisherAsync(cancellationToken);
            return rows;
        }


        /// <summary>
        /// 保存前操作
        /// </summary>
        protected virtual void SaveChangesBefore()
        {
            foreach (EntityEntry entry in ChangeTracker.Entries())
            {
                switch (entry.State)
                {
                    case EntityState.Added:
                        AddedBefore(entry);
                        break;

                    case EntityState.Modified:
                        ModifiedBefore(entry);
                        break;

                    case EntityState.Deleted:
                        DeletedBefore(entry);
                        break;
                }
            }
        }

        protected virtual void AddedBefore(EntityEntry entry)
        {
            SetVersion(entry);
            SetIsDelete(entry, false);
        }

        protected virtual void ModifiedBefore(EntityEntry entry)
        {
            SetVersion(entry);
        }

        protected virtual void DeletedBefore(EntityEntry entry)
        {
            entry.State = EntityState.Modified;
            SetVersion(entry);
            SetIsDelete(entry, true);
        }
        protected virtual void SetIsDelete(EntityEntry entry, bool isDelete)
        {
            entry.CurrentValues[DbConst.IsDelete] = isDelete;
        }
        protected virtual void SetVersion(EntityEntry entry)
        {
            entry.CurrentValues[DbConst.VersionLock] = Guid.NewGuid();
        }
        protected virtual async Task SaveDomainEventAsync(ChangeTracker changeTracker, CancellationToken cancellationToken = default)
        {
            List<EntityEntry<IAggregateRoot>> domainEntities = changeTracker
                .Entries<IAggregateRoot>()
                .Where(entry => entry.Entity.GetDomainEvents().Any())
                .ToList();

            List<IDomainEvent> domainEvents = domainEntities
                .SelectMany(entry => entry.Entity.GetDomainEvents())
                .ToList();

            domainEntities
                .ForEach(entity => entity.Entity.ClearDomainEvents());

            foreach (IDomainEvent? domainEvent in domainEvents)
            {
                await _publisherHandler.SaveAsync(domainEvent, cancellationToken);
            }
        }
        protected virtual async Task PublisherAsync(CancellationToken cancellationToken)
        {
            await _publisherHandler.PublisherAsync(cancellationToken);
        }

        #endregion

        public IQueryable<TAggregateRoot> GetQueryableAsNoTracking<TAggregateRoot>() where TAggregateRoot : class, IAggregateRoot
        {
            return Set<TAggregateRoot>().AsNoTracking();
        }

        public IQueryable<TAggregateRoot> GetQueryable<TAggregateRoot>() where TAggregateRoot : class, IAggregateRoot
        {
            return Set<TAggregateRoot>();
        }

        public new async Task AddAsync<TAggregateRoot>(TAggregateRoot aggregateRoot, CancellationToken cancellationToken) where TAggregateRoot : class, IAggregateRoot
        {
            await AddAsync(aggregateRoot, cancellationToken);
        }

        public async Task UpdateAsync<TAggregateRoot>(TAggregateRoot aggregateRoot, CancellationToken cancellationToken = default) where TAggregateRoot : class, IAggregateRoot
        {
            EntityEntry<TAggregateRoot> entry = Entry(aggregateRoot);

            EntityEntry<TAggregateRoot>? oldEntry = ChangeTracker.Entries<TAggregateRoot>().FirstOrDefault(x => x.Entity.Equals(aggregateRoot));
            if (oldEntry != null)
            {
                oldEntry.CurrentValues.SetValues(aggregateRoot);
                await Task.CompletedTask;
            }
            if (entry.State == EntityState.Detached)
            {
                _ = Update(aggregateRoot);
            }
            await Task.CompletedTask;
        }

        public async Task RemoveAsync<TAggregateRoot>(TAggregateRoot aggregateRoot, CancellationToken cancellationToken = default) where TAggregateRoot : class, IAggregateRoot
        {
            await RemoveAsync(aggregateRoot, cancellationToken);
        }

        public async Task<TAggregateRoot?> FirstOrDefaultAsync<TAggregateRoot>(IQueryable<TAggregateRoot> aggregateRootQueryable, CancellationToken cancellationToken)
        {
            return await aggregateRootQueryable.FirstOrDefaultAsync(cancellationToken);
        }

        public async Task<List<TAggregateRoot>> ToListAsync<TAggregateRoot>(IQueryable<TAggregateRoot> aggregateRootQueryable, CancellationToken cancellationToken)
        {
            return await aggregateRootQueryable.ToListAsync(cancellationToken);
        }
    }
}
